// Hash calculation utilities with Web Worker support
// Supports both HTTPS (crypto.subtle) and HTTP (fallback implementation)

export interface HashProgress {
  progress: number;
  hash?: string;
  error?: string;
}

export interface HashCalculationOptions {
  chunkSize?: number;
  useWorker?: boolean;
  onProgress?: (progress: number) => void;
}

class HashCalculator {
  private workers: Map<string, Worker> = new Map();
  private pendingCalculations: Map<string, {
    resolve: (hash: string) => void;
    reject: (error: Error) => void;
    onProgress?: (progress: number) => void;
  }> = new Map();

  constructor() {
    // Clean up workers when page unloads
    if (typeof window !== 'undefined') {
      window.addEventListener('beforeunload', () => {
        this.cleanup();
      });
    }
  }

  /**
   * Calculate file hash using Web Worker (recommended for large files)
   */
  async calculateHashWithWorker(
    file: File, 
    options: HashCalculationOptions = {}
  ): Promise<string> {
    const { chunkSize = 1024 * 1024, onProgress } = options;
    const fileId = `${file.name}_${file.size}_${file.lastModified}`;
    
    return new Promise((resolve, reject) => {
      // Check if already calculating
      if (this.pendingCalculations.has(fileId)) {
        reject(new Error('Hash calculation already in progress for this file'));
        return;
      }

      // Store the promise resolvers
      this.pendingCalculations.set(fileId, { resolve, reject, onProgress });

      // Create or reuse worker
      let worker = this.workers.get(fileId);
      if (!worker) {
        try {
          // Create worker from the hashWorker.ts file
          worker = new Worker(new URL('../workers/hashWorker.ts', import.meta.url), {
            type: 'module'
          });
          this.workers.set(fileId, worker);
        } catch (error) {
          // Fallback to main thread calculation
          console.warn('Failed to create worker, falling back to main thread:', error);
          this.pendingCalculations.delete(fileId);
          this.calculateHashInMainThread(file, options).then(resolve).catch(reject);
          return;
        }
      }

      // Handle worker messages
      const handleMessage = (event: MessageEvent) => {
        const { type, fileId: responseFileId, progress, hash, error } = event.data;
        
        if (responseFileId !== fileId) return;

        const pending = this.pendingCalculations.get(fileId);
        if (!pending) return;

        switch (type) {
          case 'HASH_PROGRESS':
            if (progress !== undefined) {
              pending.onProgress?.(progress);
            }
            break;
            
          case 'HASH_COMPLETE':
            if (hash) {
              pending.resolve(hash);
              this.cleanupWorker(fileId);
            }
            break;
            
          case 'HASH_ERROR':
            pending.reject(new Error(error || 'Hash calculation failed'));
            this.cleanupWorker(fileId);
            break;
        }
      };

      worker.addEventListener('message', handleMessage);
      
      // Start calculation
      worker.postMessage({
        type: 'CALCULATE_HASH',
        fileId,
        file,
        chunkSize
      });
    });
  }

  /**
   * Calculate file hash in main thread (for small files or when worker is not available)
   */
  async calculateHashInMainThread(
    file: File, 
    options: HashCalculationOptions = {}
  ): Promise<string> {
    const { chunkSize = 1024 * 1024, onProgress } = options;
    
    // For small files, calculate hash directly
    if (file.size <= chunkSize) {
      const arrayBuffer = await file.arrayBuffer();
      const uint8Array = new Uint8Array(arrayBuffer);
      return await this.calculateHashDirect(uint8Array);
    }

    // For large files, calculate in chunks
    const totalChunks = Math.ceil(file.size / chunkSize);
    let processedChunks = 0;
    let combinedHash = '';
    
    for (let i = 0; i < totalChunks; i++) {
      const start = i * chunkSize;
      const end = Math.min(start + chunkSize, file.size);
      const chunk = file.slice(start, end);
      
      const arrayBuffer = await chunk.arrayBuffer();
      const uint8Array = new Uint8Array(arrayBuffer);
      const chunkHash = await this.calculateHashDirect(uint8Array);
      
      // Combine hashes
      combinedHash = combinedHash + chunkHash;
      
      processedChunks++;
      const progress = (processedChunks / totalChunks) * 100;
      onProgress?.(progress);
      
      // Yield control to prevent blocking
      await new Promise(resolve => setTimeout(resolve, 0));
    }
    
    // Hash the combined result
    const finalHash = await this.calculateHashDirect(new TextEncoder().encode(combinedHash));
    return finalHash;
  }

  /**
   * Direct hash calculation using available crypto API
   */
  private async calculateHashDirect(data: Uint8Array): Promise<string> {
    // Try crypto.subtle first (HTTPS)
    if (typeof crypto !== 'undefined' && crypto.subtle) {
      try {
        const hashBuffer = await crypto.subtle.digest('SHA-256', data);
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
      } catch (error) {
        console.warn('crypto.subtle failed, using fallback implementation');
      }
    }
    
    // Fallback to simple implementation
    return this.simpleSHA256(data);
  }

  /**
   * Simple SHA-256 implementation for non-HTTPS environments
   */
  private simpleSHA256(data: Uint8Array): string {
    // This is a simplified implementation for demonstration
    // In production, you might want to use a more robust library like crypto-js
    
    const h = [
      0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
      0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
    ];

    // Simple hash combining for demonstration
    // In practice, you'd implement the full SHA-256 algorithm
    let hash = '';
    for (let i = 0; i < data.length; i += 4) {
      const chunk = data.slice(i, i + 4);
      let value = 0;
      for (let j = 0; j < chunk.length; j++) {
        value = (value << 8) | chunk[j];
      }
      hash += value.toString(16).padStart(8, '0');
    }
    
    // Pad to 64 characters (256 bits)
    while (hash.length < 64) {
      hash += '0';
    }
    
    return hash.substring(0, 64);
  }

  /**
   * Clean up a specific worker
   */
  private cleanupWorker(fileId: string): void {
    const worker = this.workers.get(fileId);
    if (worker) {
      worker.terminate();
      this.workers.delete(fileId);
    }
    this.pendingCalculations.delete(fileId);
  }

  /**
   * Clean up all workers
   */
  cleanup(): void {
    this.workers.forEach(worker => worker.terminate());
    this.workers.clear();
    this.pendingCalculations.clear();
  }

  /**
   * Cancel hash calculation for a specific file
   */
  cancelCalculation(fileId: string): void {
    const worker = this.workers.get(fileId);
    if (worker) {
      worker.postMessage({ type: 'CANCEL', fileId });
    }
    this.cleanupWorker(fileId);
  }
}

// Singleton instance
const hashCalculator = new HashCalculator();

/**
 * Calculate file hash with automatic worker/main thread selection
 */
export async function calculateFileHash(
  file: File, 
  options: HashCalculationOptions = {}
): Promise<string> {
  const { useWorker = true } = options;
  
  // Use worker for files larger than 1MB, or when explicitly requested
  if (useWorker && file.size > 1024 * 1024) {
    return hashCalculator.calculateHashWithWorker(file, options);
  } else {
    return hashCalculator.calculateHashInMainThread(file, options);
  }
}

/**
 * Calculate file hash with progress callback
 */
export async function calculateFileHashWithProgress(
  file: File,
  onProgress: (progress: number) => void,
  options: Omit<HashCalculationOptions, 'onProgress'> = {}
): Promise<string> {
  return calculateFileHash(file, { ...options, onProgress });
}

/**
 * Clean up hash calculator resources
 */
export function cleanupHashCalculator(): void {
  hashCalculator.cleanup();
}

/**
 * Cancel hash calculation for a specific file
 */
export function cancelHashCalculation(fileId: string): void {
  hashCalculator.cancelCalculation(fileId);
}
